﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq.Expressions;
using System.Reflection;

namespace General.Utility
{
    /// <summary>
    /// 数据转换类
    /// </summary>
    public class DataTransformation
    {
        /// <summary>
        /// 对象缓存 作者：唐晓军
        /// </summary>
        private static ConcurrentDictionary<Type, Delegate> ExpressionCache = new ConcurrentDictionary<Type, Delegate>();

        /// <summary>
        /// 将数据表转换为实体类。
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="dt">数据表</param>
        /// <returns></returns>
        public static List<T> ConvertDataTableToEntityList<T>(DataTable dt) where T : new()
        {
            var type = typeof(T);
            var list = new List<T>();
            if (dt.Rows.Count == 0)
            {
                return list;
            }

            var pros = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            foreach (DataRow dr in dt.Rows)
            {
                var t = new T();
                foreach (var p in pros)
                {
                    if (p.CanWrite)
                    {
                        if (dt.Columns.Contains(p.Name) && !Convert.IsDBNull(dr[p.Name]))
                        {
                            p.SetValue(t, dr[p.Name], null);
                        }
                    }
                }
                list.Add(t);
            }
            return list;
        }

        /// <summary>
        /// 将数据某条记录转换为实体类。
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="dt">数据对象</param>
        /// <returns></returns>
        public static T ConvertDataTableToEntity<T>(DataTable dt) where T : new()
        {
            if (dt.Rows.Count == 0)
            {
                return default(T);
            }

            var type = typeof(T);
            var result = new T();
            var pros = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            foreach (DataRow dr in dt.Rows)
            {
                var t = new T();
                foreach (var p in pros)
                {
                    if (p.CanWrite)
                    {
                        if (dt.Columns.Contains(p.Name) && !Convert.IsDBNull(dr[p.Name]))
                        {
                            p.SetValue(t, dr[p.Name], null);
                        }
                    }
                }

                result = t;
            }
            return result;
        }

        /// <summary>
        /// 将数据表转换为实体类。
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="reader">数据对象</param>
        /// <returns></returns>
        public static List<T> ConvertDataReaderToEntityList<T>(SqlDataReader reader) where T : new()
        {
            List<T> list = new List<T>();
            while (reader.Read())
            {
                T t = System.Activator.CreateInstance<T>();
                Type obj = t.GetType();
                for (int i = 0; i < reader.FieldCount; i++)
                {
                    object tempValue = null;
                    if (reader.IsDBNull(i))
                    {
                        if (obj.GetProperty(reader.GetName(i)) != null)
                        {
                            string typeFullName = obj.GetProperty(reader.GetName(i)).PropertyType.FullName;
                            tempValue = GetDBNullValue(typeFullName);
                        }
                    }
                    else
                    {
                        tempValue = reader.GetValue(i);
                    }
                    if (obj.GetProperty(reader.GetName(i)) != null)
                    {
                        obj.GetProperty(reader.GetName(i)).SetValue(t, tempValue, null);
                    }

                }
                list.Add(t);
            }
            return list;
        }

        /// <summary>
        /// 将数据某条记录转换为实体类。
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="reader">数据对象</param>
        /// <returns></returns>
        public static T ConvertDataReaderToEntity<T>(SqlDataReader reader) where T : new()
        {
            if (!reader.HasRows)
            {
                return default(T);
            }
            var type = typeof(T);
            var result = new T();
            var pros = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            if (reader.Read())
            {
                var t = new T();
                foreach (var p in pros)
                {
                    try
                    {
                        if (p.CanWrite)
                        {
                            if (reader.GetOrdinal(p.Name) != -1 && !Convert.IsDBNull(reader[p.Name]))
                            {
                                p.SetValue(t, reader[p.Name], null);
                            }
                        }
                    }
                    catch
                    {
                        continue;
                    }
                }
                result = t;
            }
            return result;
        }

        /// <summary>
        /// 使用Delegate的方式将SqlDataReader转成List
        /// 作者：唐晓军
        /// <remark>注意：属性的类型必须要与数据库一致</remark>
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="reader">数据对象</param>
        /// <returns></returns>
        public static List<T> ConvertDataReaderToDelegateList<T>(SqlDataReader reader)
        {
            List<T> models = new List<T>();
            List<string> proNames = new List<string>();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                proNames.Add(reader.GetName(i));
            }
            Func<SqlDataReader, T> readRow = GetReader<T>(proNames);
            if (reader.HasRows)
            {
                while (reader.Read())
                {
                    //注意数据类型必须与数据库一致
                    models.Add(readRow(reader));
                }
            }
            return models;
        }

        /// <summary>
        /// 使用Delegate的方式将SqlDataReader转成对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static T ConvertDataReaderToDelegate<T>(SqlDataReader reader)
        {
            T model = default(T);
            List<string> proNames = new List<string>();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                proNames.Add(reader.GetName(i));
            }
            Func<SqlDataReader, T> readRow = GetReader<T>(proNames);
            if (reader.HasRows)
            {
                while (reader.Read())
                {
                    model = readRow(reader);
                }
            }
            return model;
        }

        /// <summary>
        /// 优化通过反射为对象进行赋值性能
        /// 作者：唐晓军
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private static Func<SqlDataReader, T> GetReader<T>(List<string> param)
        {
            Delegate resDelegate;
            if (!ExpressionCache.TryGetValue(typeof(T), out resDelegate))
            {
                // Get the indexer property of SqlDataReader 
                var indexerProperty = typeof(SqlDataReader).GetProperty("Item", new[] { typeof(string) });
                // List of statements in our dynamic method 
                var statements = new List<Expression>();
                // Instance type of target entity class 
                ParameterExpression instanceParam = Expression.Variable(typeof(T));
                // Parameter for the SqlDataReader object
                ParameterExpression readerParam = Expression.Parameter(typeof(SqlDataReader));

                // Create and assign new T to variable. Ex. var instance = new T(); 
                BinaryExpression createInstance = Expression.Assign(instanceParam, Expression.New(typeof(T)));
                statements.Add(createInstance);

                foreach (var property in typeof(T).GetProperties())
                {
                    if (!param.Contains(property.Name))
                        continue;
                    // instance.Property 
                    MemberExpression getProperty = Expression.Property(instanceParam, property);
                    // row[property] The assumption is, column names are the 
                    // same as PropertyInfo names of T 
                    IndexExpression readValue = Expression.MakeIndex(readerParam, indexerProperty, new[] { Expression.Constant(property.Name) });

                    // 为属性赋值
                    BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(readValue, property.PropertyType));

                    statements.Add(assignProperty);
                }
                var returnStatement = instanceParam;
                statements.Add(returnStatement);

                var body = Expression.Block(instanceParam.Type,
                    new[] { instanceParam }, statements.ToArray());

                var lambda = Expression.Lambda<Func<SqlDataReader, T>>(body, readerParam);
                resDelegate = lambda.Compile();

                // 将动态方法保存到缓存中
                ExpressionCache[typeof(T)] = resDelegate;
            }
            return (Func<SqlDataReader, T>)resDelegate;
        }

        /// <summary>  
        /// 返回值为DBnull的默认值  
        /// </summary>  
        /// <param name="typeFullName">数据类型的全称，例：System.Int32</param>  
        /// <returns>返回的默认值</returns>  
        private static object GetDBNullValue(string typeFullName)
        {
            if (typeFullName == "System.String")
            {
                return String.Empty;
            }

            if (typeFullName == "System.Int32")
            {
                return 0;
            }
            if (typeFullName == "System.DateTime")
            {
                return DateTime.MinValue;
            }
            if (typeFullName == "System.Boolean")
            {
                return false;
            }
            return null;
        }
    }
}
